/*
 * Tezad - Classic puzzle game
 * Copyright 2009 Shayne Riley and Paul Maseberg
 *
 * Tezad is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tezad is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tezad.  If not, see <http://www.gnu.org/licenses/>
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * To contact the authors on questions or issues regarding the license,
 * you may send an email to <pezads at gmail dot com>
 */

#include "GameCore.h"
#include <iostream>
#include <iterator>
#include <algorithm>
#include "GameActions.h"
#include "GameMinoEngine.h"

GameCore::GameCore()
: well(22, 10, 2), startingRow(20), startingCol(4), speed(0), currentStep(0),
  gravityStep(0), settingStep(0)
{
    // Some more default values.
    activeMino = &GameMinoStruct::mino(0);
    minoState = GameMinoState(startingRow, startingCol, 0);
    minoEngine = GameMinoEngine();

    currentQueue = InputQueue();

    for (int i = 0; i < 3; ++i)
    {
        heldButtons[i] = false;
        buttonStep[i] = 0;
    }
}

GameCore::~GameCore() { /* Do nothing. */ }

void GameCore::initializeGame(int width, int totalHeight, int hiddenHeight,
        int startRow, int startCol, int startSpeed)
{
    well = GameWell(totalHeight, width, hiddenHeight);
    startingRow = startRow;
    startingCol = startCol;
    speed = startSpeed;
    currentStep = 0;
    gravityStep = 0;
    settingStep = 0;

    // Some more default values.
    activeMino = &GameMinoStruct::mino(0);
    minoState = GameMinoState(startingRow, startingCol, 0);
    minoEngine = GameMinoEngine();

    currentQueue = InputQueue();

    CommandList commands = CommandList();

    Command *pCommand = new InitGameAction(width, totalHeight, hiddenHeight,
            startRow, startCol, startSpeed);

    commands.push_back(pCommand);
    announceCommands(commands);
    deleteCommands(commands);
}

void GameCore::advanceTimeStep(long timeStep)
{
    CommandList commands = CommandList();

    // Should check to make sure that time step is, in fact, positive.
    // For now these loop conditions will do nicely.
    for (int i = 0; i < timeStep; ++i)
    {
        ++currentStep;

        // do input checks. (make sure to account for multiple commands within
        //    the same step. Although this might be rare for humans, it could
        //    be possible for super-human AI, I suppose.) If the user performs
        //    a move that creates an illegal situation, respond as follows:
        //    - if the player wants to move left but can't then do not move left
        //    - same thing for if the player wants to move right
        //    - if the piece was rotated counterclockwise, first check if it
        //      can be moved to the left one brick, if not, check if it can
        //      be moved to the right. The I tetromino is a special case: check
        //      if it can be moved to the left two bricks, if not, check if it
        //      can move to the right two. If not, check if it can be shifted
        //      up. If it can't, repeat the horizontal shifting again. If
        //      it still can't, shift up again. If it still can't, repeat
        //      horizontal shifting. But that is it. This is Shayne's gross
        //      estimation of what the Super Rotation System (SRS) is, but it is
        //      off in more than one way. So you can call it the Shayne
        //      Rotation System (SRS) instead.
        //    - if the piece was rotated clockwise, everything remains the same
        //      except the right side is checked first, then the left.
        //    - Hmm, there are some other rotation rules too, such as if the
        //      I piece is rotated to a horizontal position then it should be
        //      shifted down or something. Find it at
        //      http://www.tetrisconcept.com/wiki/index.php/I-spins_in_SRS
        //    - another good page is at
        //      http://www.tetrisconcept.com/wiki/index.php/List_of_twists
        bool hardDropped = performInputs(commands);

        // do gravity. This means to only drop the tetromino at a specified
        //    rate if legal. If a hard drop is specified, skip over this step
        //    because the tetromino should be considered to be at the lowest
        //    point it can get.
        //    It does not have the responsibility of also setting
        //    the tetromino if there is no legal space to drop down.
        //    (The ordering of the input checks before the gravity is
        //    important. Take a look at the section "20G" of the webpage
        //    http://www.tetrisconcept.com/wiki/index.php/SRS#20G )
        performGravity(commands, hardDropped);

        // do placement checks. If a hard drop was called, then set the
        //  tetromino at its lowest legal location.
        bool hasSet = performSets(commands, hardDropped);

        // Do line clearing checks. Note that this should only occur after
        // a tetromino has occured.
        performClearing(commands, hasSet);

        // Grab next tetromino and make it active. This should occur at the next
        // timestep after a set, or after a few timesteps of 1-3 lines being
        // cleared, or after even more timesteps for 4 lines cleared.
        performNextMino(commands, hasSet);
    }

    if (!commands.empty()) {
        announceCommands(commands);
        deleteCommands(commands);
    }
}

long GameCore::currentTimeStep()
{
    return currentStep;
}

bool GameCore::performInputs(CommandList& commands)
{
    // Holy cow, is this thing thread-unsafe or what?! Note: make this atomic.

    // Do the necessary repeats on the three buttons that might be held down.
    for (int i = 0; i < 3; ++i)
    {
        if (heldButtons[i])
        {
            ++buttonStep[i];
        }
    }
    if (buttonStep[0] >= REPEAT_DELAY)
    {
        currentQueue.push(left);
        buttonStep[0] = REPEAT_DELAY - REPEAT_RATE;
    }
    if (buttonStep[1] >= REPEAT_DELAY)
    {
        currentQueue.push(right);
        buttonStep[1] = REPEAT_DELAY - REPEAT_RATE;
    }
    if (buttonStep[2] >= REPEAT_RATE) // Soft has no delay.
    {
        currentQueue.push(soft);
        buttonStep[2] = 0;
    }

    // Munch through the queue to the end of the queue or to a stopping point.
    bool hardDrop = false;
    while (!currentQueue.empty() && !hardDrop) {
        const EnInputActs act = currentQueue.front();

        GameMinoState test = minoState;

        int actionType = 0;

        switch (act)
        {
        case left:
            test = minoState.shiftLeft();
            actionType = MovementAction::MA_SHIFT_LEFT;
            break;
        case right:
            test = minoState.shiftRight();
            actionType = MovementAction::MA_SHIFT_RIGHT;
            break;
        case soft:
            test = minoState.shiftDown();
            actionType = MovementAction::MA_SOFT_DOWN;
            gravityStep = 0;
            break;
        case hard:
            test = minoEngine.settingMinoState(well, minoState, *activeMino);
            actionType = MovementAction::MA_HARD_DOWN;
            hardDrop = true;
            break;
        case ccw:
            test = minoState.rotateCCW();
            actionType = MovementAction::MA_ROTATE_COUNTERCLOCKWISE;
            break;
        case cw:
            test = minoState.rotateCW();
            actionType = MovementAction::MA_ROTATE_CLOCKWISE;
            break;
        case swap:
            // Do nothing! BWAHAHA!
            break;
        default:
            // wha?!
            break;
        }

        if (actionType != 0 &&
                minoEngine.isMinoPlacementLegal(well, test, *activeMino))
        {
            // Allocating this here, I'm sure, will come back to bite me.
            Command *pCommand = new MovementAction(actionType);
            commands.push_back(pCommand);
            minoState = test;
        }

        currentQueue.pop();
    }

    return hardDrop;
}

void GameCore::performGravity(CommandList& commands, bool inHardDrop)
{
    if (!inHardDrop)
    {
        ++gravityStep;
        if (gravityStep >= 60 - (speed * 3))
        {
            gravityStep = 0;
            GameMinoState test = minoState.shiftDown();
            if (minoEngine.isMinoPlacementLegal(well, test, *activeMino))
            {
                minoState = test;
                Command *pCommand = new MovementAction(MovementAction::MA_GRAVITY_DOWN);
                commands.push_back(pCommand);
            }
        }
    }
}

bool GameCore::performSets(CommandList& commands, bool inHardDrop)
{
    bool result = inHardDrop;
    if (!inHardDrop)
    {
        if (!minoEngine.isMinoPlacementLegal(well, minoState.shiftDown(), *activeMino))
        {
            ++settingStep;
            result = settingStep >= 60;
        }
    }

    if (result)
    {
        minoEngine.setMino(well, minoState, *activeMino);
        Command *pCommand = new SetMinoAction();
        commands.push_back(pCommand);
    }

    return result;
}

void GameCore::performClearing(CommandList& commands, bool inHasSet)
{
    if (inHasSet)
    {
        int cleareds[4];
        int numCleareds = 0;

        // Only need to loop through the rows that the mino was set in.
        int setRows[4];
        int setCols[4];
        activeMino->bricks(minoState, setRows, setCols);

        for (int i = 0; i < 4; ++i)
        {
            int row = setRows[i];
            bool makeCleared = true;
            for (int col = 0; col < well.width(); ++col)
            {
                if (well.brick(row, col) == 0)
                {
                    makeCleared = false;
                    break;
                }
            }

            if (makeCleared)
            {
                well.clearRow(row);
                cleareds[numCleareds] = row;
                ++numCleareds;
            }
        }

        if (numCleareds > 0)
        {
            Command *pCommand = new ClearRowsAction(cleareds, numCleareds);
            commands.push_back(pCommand);
        }
    }
}

void GameCore::performNextMino(CommandList& commands, bool inHasSet)
{
    if (inHasSet)
    {
        gravityStep = 0;
        settingStep = 0;
        minoState = GameMinoState(startingRow, startingCol, 0);
        // Too lazy to even use the default rand? You bet.
        int nextMino = (activeMino->brickStyle()) % 7;
        activeMino = &GameMinoStruct::mino(nextMino);

        Command *pCommand = new NewMinoAction(nextMino);
        commands.push_back(pCommand);
    }
}

void GameCore::announceCommands(CommandList& commands)
{
    std::vector<Command*>::const_iterator first = commands.begin();
    std::vector<Command*>::const_iterator last = commands.end();
    std::vector<Command*>::const_iterator iter;
    for (iter = first; iter != last; ++iter)
    {
        Command *command = *iter;
        notify(*command);
    }
}

void GameCore::deleteCommands(CommandList& commands)
{
    std::vector<Command*>::const_iterator first = commands.begin();
    std::vector<Command*>::const_iterator last = commands.end();
    std::vector<Command*>::const_iterator iter;
    for (iter = first; iter != last; ++iter)
    {
        Command *command = *iter;
        delete command;
    }

    commands.clear();
}

void GameCore::inputReqShiftLeft(bool pressed)
{
    heldButtons[0] = pressed;
    buttonStep[0] = 0;
    if (pressed)
    {
        heldButtons[1] = false; // Deactivate move right, in case both are down.
        currentQueue.push(left);
    }
}

void GameCore::inputReqShiftRight(bool pressed)
{
    heldButtons[1] = pressed;
    buttonStep[1] = 0;
    if (pressed)
    {
        heldButtons[0] = false; // Deactivate move left, in case both are down.
        currentQueue.push(right);
    }
}

void GameCore::inputReqSoftDown(bool pressed)
{
    heldButtons[2] = pressed;
    buttonStep[2] = 0;
    if (pressed)
    {
        currentQueue.push(soft);
    }
}

void GameCore::inputReqHardDown()
{
    currentQueue.push(hard);
}

void GameCore::inputReqRotateCCW()
{
    currentQueue.push(ccw);
}

void GameCore::inputReqRotateCW()
{
    currentQueue.push(cw);
}

void GameCore::inputReqSwapActive()
{
    currentQueue.push(swap);
}
